﻿/* ****************************************************************************
 *
 * Copyright (c) Francesco Abbruzzese. All rights reserved.
 * francesco@dotnet-programming.com
 * http://www.dotnet-programming.com/
 * 
 * This software is subject to a dual license model
 * and included in the license.txt file of this distribution.
 * 
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using System.Windows.Data;
using System.Globalization;
using System.Text;
using System.Dynamic;
using System.Collections;
using System.Collections.ObjectModel;
using BindToolkit.DataAnnotations;
using System.ServiceModel;

namespace BindToolkit
{
    public partial class BindWrapper
    {

        public void AddValidationFaultExceptionContent(IEnumerable<ValidationErrorInfo> errors)
        {
            
            List<ValidationException> exceptions = new List<ValidationException>();
            foreach (ValidationErrorInfo error in errors)
            {
                exceptions.Add(
                    new System.ComponentModel.DataAnnotations.ValidationException(
                        new System.ComponentModel.DataAnnotations.ValidationResult(error.Error, error.Members),
                        null, null)
                    );
            }
            AddRemoteErrors(exceptions);
        }
        protected string getFormatError(PropertyInfo pi, object value)
        {
            if (value == null || value.GetType() == typeof(string))
            {
                string name = pi.Name;
                DisplayAttribute[] da = pi.GetCustomAttributes(typeof(DisplayAttribute), true) as DisplayAttribute[];
                if (da != null && da.Length > 0) name = da[0].GetName();
                FormatErrorAttribute[] err = pi.GetCustomAttributes(typeof(FormatErrorAttribute), true) as FormatErrorAttribute[];
                if (err != null && err.Length > 0)
                {
                    string errorFormat = err[0].GetErrorMessage();
                    if (errorFormat != null) return string.Format(errorFormat, name, value.ToString());
                    else return null;
                }
                else return null;
            }
            else return null;
        }
        public static ObservableCollection<BindWrapper> WrapEnumerable(IEnumerable source, bool wrapDeep = false, bool wrapEnumerables=false)
        {
            List<BindWrapper> pr = new List<BindWrapper>();
            if (source != null)
                foreach (object o in source) pr.Add(new BindWrapper(o, wrapDeep, wrapEnumerables));
            return new ObservableCollection<BindWrapper>(pr);
        }
        public static ObservableDictionary<string, BindWrapper> WrapDictionary<TValue>(IDictionary<string, TValue> source, bool wrapDeep = false, bool wrapEnumerables = false)
        {
            return new ObservableDictionary<string, BindWrapper>(ObservableDictionary<string, TValue>.RecursiveWrapDictionary(source, wrapDeep, wrapEnumerables));
        }
        public static List<T> UnWrapEnumerable<T>(ObservableCollection<BindWrapper> source)
        {
            List<T> res = new List<T>();
            if (source != null)
            {
                foreach (BindWrapper bw in source)
                {
                    T item = (T)bw.WrappedObject;
                    res.Add(item);
                }
            }
            return res;
        }
        public static System.ComponentModel.DataAnnotations.ValidationResult AddPrefix(string prefix, System.ComponentModel.DataAnnotations.ValidationResult val)
        {
            if (string.IsNullOrWhiteSpace(prefix) || val == null) return val;
            List<string> res = null;
            foreach (string x in val.MemberNames)
            {
                if (res == null) res = new List<string>();
                res.Add(prefix + "." + val.MemberNames);
            }
            if (res == null) return val;
            else
            {
                return new System.ComponentModel.DataAnnotations.ValidationResult(val.ErrorMessage, res);
            }
        }
        public static IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> AddPrefix(string prefix, IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> enVal)
        {
            if (prefix == null || enVal == null) return enVal;
            List<System.ComponentModel.DataAnnotations.ValidationResult> res = null;
            foreach (System.ComponentModel.DataAnnotations.ValidationResult val in enVal)
            {
                if (res == null) res = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
                res.Add(AddPrefix(prefix, val));
            }
            if (res == null) return enVal;
            else return res;
        }

        protected void AddToCollections(string field, object value)
        {
            if (collections == null) collections = new Dictionary<string, object>();
            collections[field] = value;
        }
        protected bool RecursiveWrapCollectionTree(ref object val)
        {
            if (val == null) return false;
            Type t = val.GetType();
            if (t.GetInterface("IDictionary`2", false) != null)
            {
                Type dictionaryInterface = t.GetInterface("IDictionary`2", false);
                Type[] argTypes = dictionaryInterface.GetGenericArguments();
                if (argTypes[0] != typeof(string)) return false;

                Type observableDictionaryType = typeof(ObservableDictionary<string, object>).GetGenericTypeDefinition().MakeGenericType(argTypes);

                if (argTypes[1].GetInterface("IConvertible", false) != null)
                {
                    Type constructorParameterType = typeof(IDictionary<string, object>).GetGenericTypeDefinition().MakeGenericType(new Type[] { typeof(string), argTypes[1] });

                    ConstructorInfo ci = observableDictionaryType.GetConstructor(new Type[] { constructorParameterType });
                    val = ci.Invoke(new object[] { val });
                    return true;
                }
                else if (argTypes[1] != typeof(object) && argTypes[1].GetInterface("IEnumerable", false) == null)
                {
                    MethodInfo mi = observableDictionaryType.GetMethod("RecursiveWrapDictionary", System.Reflection.BindingFlags.Static | BindingFlags.Public);
                    object wrappedDictionary = mi.Invoke(null, new object[] { val, true, true });

                    val = new ObservableDictionary<string, BindWrapper>(wrappedDictionary as ObservableDictionary<string, BindWrapper>);
                    return true;

                }
                else
                {
                    // Renounce to strongly typed values...either nested enumerable or mixed types
                    MethodInfo mi = t.GetMethod("GetEnumerator", new Type[0]);
                    IEnumerator en = mi.Invoke(val, new object[0]) as IEnumerator;
                    IDictionary<string, object> translatedDictionary = new Dictionary<string, object>();
                    bool res = false;
                    while (en.MoveNext())
                    {
                        object currEl = en.Current;
                        if (currEl == null) continue;
                        Type currType = currEl.GetType();

                        PropertyInfo pi = currType.GetProperty("Key");
                        object key = pi.GetValue(currEl, new object[0]);

                        pi = currType.GetProperty("Value");
                        object value = pi.GetValue(currEl, new object[0]);
                        if (value == null) continue;
                        Type ct=value.GetType();
                        if (ct.GetInterface("IDictionary`2", false) != null || ct.GetInterface("IEnumerable`1", false) != null)
                        {
                            object newValue = value;

                            res = RecursiveWrapCollectionTree(ref newValue) || res;

                            translatedDictionary.Add(new KeyValuePair<string, object>(key as string, newValue));
                        }
                        else if (ct.GetInterface("IConvertible", false) != null || ct.GetInterface("IEnumerable", false)!= null)
                        {
                            translatedDictionary.Add(new KeyValuePair<string, object>(key as string, val));
                        }

                        else
                        {
                            translatedDictionary.Add(new KeyValuePair<string, object>(key as string, new BindWrapper(val, true, true)));
                        }
                    }
                    val = new ObservableDictionary<string, object>(translatedDictionary);
                    return true;  
                }

            }
            else if (t.GetInterface("IEnumerable`1", false) != null && t != typeof(string))
            {
                Type enumerableInterface = t.GetInterface("IEnumerable`1", false);
                Type[] argTypes = enumerableInterface.GetGenericArguments();

                Type observableCollectionType = typeof(ObservableCollection<string>).GetGenericTypeDefinition().MakeGenericType(new Type[]{argTypes[0]});

                if (argTypes[0].GetInterface("IConvertible", false) != null)
                {
                    Type constructorParameterType = typeof(IEnumerable<string>).GetGenericTypeDefinition().MakeGenericType(new Type[] { argTypes[0] });

                    ConstructorInfo ci = observableCollectionType.GetConstructor(new Type[] { constructorParameterType });
                    val = ci.Invoke(new object[] { val });
                    return true;
                }
                else if ( argTypes[0] != typeof(object) && argTypes[0].GetInterface("IEnumerable", false) == null)
                {
                    val = WrapEnumerable(val as IEnumerable, true, true);
                    return true;
                }
                else
                {
                        // Renounce to strongly typed values...either too many nested enumerables or mixed types
                        IEnumerable enumerable = val as IEnumerable;

                        IList<object> translatedList = new List<object>();
                        bool res = false;
                        foreach (object value in enumerable)
                        {

                           
                            if (value == null) continue;
                            Type ct = val.GetType();
                            if (ct.GetInterface("IDictionary`2", false) != null || ct.GetInterface("IEnumerable`1", false) != null)
                            {
                                object newValue = value;

                                res = RecursiveWrapCollectionTree(ref newValue) || res;

                                translatedList.Add(newValue);
                            }
                            else if (ct.GetInterface("IConvertible", false) != null || ct.GetInterface("IEnumerable", false) != null)
                            {
                                translatedList.Add(val);
                            }

                            else
                            {
                                translatedList.Add(new BindWrapper(val, true, true));
                            }


                        }
                        val = new ObservableCollection<object>(translatedList);
                        return true;
                }

            }
            else
                return false;
        }
        private Dictionary<string, ValidationDependency> dependencies;
        private void createDependenciesDictionary()
        {
            if (dependencies == null) dependencies = new Dictionary<string, ValidationDependency>();
        }
        private bool dependencyPropagated(string name)
        {
            if (dependencies == null) return false;
            if(dependencies.ContainsKey(name)) return dependencies[name].MyUpdaterInformed;
            return false;
        }
        private void addDependency(string property, PropertyLocation dependentProperty)
        {
            createDependenciesDictionary();
            if (dependencies.ContainsKey(property))
            {
                dependencies[property].ToUpdate.Add(dependentProperty);
            }
            else
            {
                ValidationDependency currVal = 
                    new ValidationDependency
                    {
                        MyUpdaterInformed = false,
                        ToUpdate = new List<PropertyLocation>()
                    };
                currVal.ToUpdate.Add(dependentProperty);
                dependencies.Add(property, currVal);
            }
        }

        private void confirmDependencyPropagated(string property)
        {
            createDependenciesDictionary();
            if (dependencies.ContainsKey(property))
            {
                dependencies[property].MyUpdaterInformed = true;
            }
            else
            {
                dependencies.Add(property,
                    new ValidationDependency
                    {
                        MyUpdaterInformed = true,
                        ToUpdate = new List<PropertyLocation>()
                    });
            }
        }

        private void propagateDownDependency(string propertyName, PropertyLocation dependentProperty)
        {
            if (string.IsNullOrWhiteSpace(propertyName)) return ;
            if (propertyName.Contains("[")) throw new NotSupportedException(string.Format(Resource.NotSupportedEnumerables, "DynamicAttribute"));
            int pos = propertyName.IndexOf('.');
            string field = null;
            string tail = null;
            if (pos < 0)
            {
                field = propertyName;
            }
            else
            {
                field = propertyName.Substring(0, pos);
                if (propertyName.Length > pos + 1)
                    tail = propertyName.Substring(pos + 1);       
            }

            PropertyInfo pi = WrappedType.GetProperty(field);

            object propertyValue = null;
            if (!TryGetMember(field, out propertyValue)) return ;
            
            if (propertyValue != null && tail != null && tail.Length > 0)
            {
                if (propertyValue.GetType() == typeof(BindWrapper))
                {

                    ((BindWrapper)propertyValue).propagateDownDependency(tail, dependentProperty);
                    
                    return;
                }
                else
                {
                    return;
                }
            }
            else
            {
                addDependency(field, dependentProperty);
                return;
            }

        }
        private void handlesValidationDependencies(string property, List<System.ComponentModel.DataAnnotations.ValidationResult> errors)
        {
            if (dependencyPropagated(property)) return;
            if (errors != null)
            {
                foreach (System.ComponentModel.DataAnnotations.ValidationResult error in errors)
                {
                    if (error.MemberNames == null) continue;
                    int i = 0;
                    foreach (string memeberName in error.MemberNames)
                    {
                        if (i == 0)
                        {
                            i++;
                            continue;
                        }
                        i++;
                        propagateDownDependency(memeberName,
                                new PropertyLocation
                                {
                                    propertyName = property,
                                    wrapper = this
                                }
                            );
                    }
                }
            }
            confirmDependencyPropagated(property);
        }
        private void triggerDependencies(string property)
        {
            if (dependencies == null) return ;
            if (dependencies.ContainsKey(property))
            {
                foreach (PropertyLocation pl in dependencies[property].ToUpdate)
                {
                    pl.wrapper.controlPropertyErrors(pl.propertyName);
                }
            }
            
        }
        protected void recursiveWrapping(bool wrapEnumerables)
        {
            foreach (PropertyInfo pi in WrappedType.GetProperties())
            {
                ParameterInfo[] pars = pi.GetIndexParameters();
                if (pars != null && pars.Length > 0) continue;
                object val = pi.GetValue(WrappedObject, new Type[0]);
                if (val == null) continue;
                Type t = val.GetType();
                if (t == typeof(BindWrapper)) continue;
                if (t.GetInterface("IConvertible", false) == null && t.GetInterface("System.Collections.IEnumerable", false) == null)
                {
                    if (wrappers == null) wrappers = new Dictionary<string, BindWrapper>();
                    wrappers[pi.Name] = new BindWrapper(val, true);
                }
                else if (wrapEnumerables)
                {
                    object newVal = val;
                    bool res = RecursiveWrapCollectionTree(ref newVal);
                    if (res)
                    {
                        AddToCollections(pi.Name, newVal);
                    }
                }
                else
                {
                    continue;
                }
            }
        }
    }
}
